// 日志落地、工厂模式的落地创建工厂

#ifndef __M_SINK_H__
#define __M_SINK_H__

#include "util.hpp"
#include <fstream>
#include <sstream>
#include <cassert>
#include <memory>

namespace snowdragon
{
    // 落地方式的基类，注意使用log，支持扩展
    class Sink
    {
    public:
        using ptr = std::shared_ptr<Sink>;      // 使用智能指针，方便使用
        // 析构函数，支持多态。在这里我没有使用到，单如果是用户使用的话，可能使用自己编写的落地方式，那该方式是否有
        // 需要用户自己释放的资源，我无所得知。析构操作交由用户处理
        virtual ~Sink() {};
        // 真正得写入操作函数得编写，因落地方式的不同而不同
        virtual void log(const char *str, size_t len) = 0;
    };
    // 标准输出
    class StdOutSink : public Sink
    {
    public:
        using ptr = std::shared_ptr<StdOutSink>;
        void log(const char *str, size_t len) // 函数名不要修改，否则没有重写父类的纯虚函数，会报错
        {
            std::cout.write(str, len);      // 标准输出，直接向其输出即可
        }
    };
    // 指定文件输出
    class specificFileSink : public Sink
    {
    public:
        using ptr = std::shared_ptr<specificFileSink>;
        specificFileSink(const std::string &path_name)
            : _path_name(path_name)
        {
            util::File::createDirectory(util::File::getPath(_path_name));       // 准备操作，写入得是已存在的文件
            _ofs.open(_path_name, std::ios::binary | std::ios::app);            // 打开该文件才能写入
            assert(_ofs.is_open());                 // 确保打开成功
        }
        const std::string &getFileName()        // 我在此项目中没有使用到，但还是提供了，方便用户输出消息时获取文件名
        {
            return _path_name;
        }
        void log(const char *str, size_t len)           // 重写虚函数，真正的写入操作
        {
            _ofs.write(str, len);
            assert(_ofs.good());        // 防止写入错误
        }

    private:
        std::string _path_name;     //要输出的指定文件的文件名
        std::ofstream _ofs;        // 方便使用，不用每调用一次函数就创建一个该对象
    };
    // 以指定文件大小进行滚动输出消息到文件中
    class RollFileBySizeSink : public Sink
    {
    public:
        using ptr = std::shared_ptr<RollFileBySizeSink>;
        RollFileBySizeSink(const std::string &base_file_path_name, size_t max_size)             // max_size无需使用int
            : _base_file_path_name(base_file_path_name), _max_size(max_size), _cur_file_size(0) // 统一在此处初始化
            , _file_count(0)
        {
            util::File::createDirectory(util::File::getPath(_base_file_path_name));     // 准备操作，创建（父）目录
            createFile();       // 创建要写入的文件
        }
        void log(const char *str, size_t len)
        {
            if (_cur_file_size > _max_size)     // 超过了指定的大小，需要"滚动"文件了
                createFile();
            _ofs.write(str, len);
            assert(_ofs.good());
            _cur_file_size += len; // 记得加值，否则将会写在一个文件内，这个记录对应文件已经写入多少的变量将无意义
        }

    private:
        void createFile()       // 创建要写入的文件
        {
            _ofs.close();       // 因为构造函数中会打开文件，所有，为了安全，得把写入完成得文件关闭，避免资源（文件描述符）泄漏
            _ofs.open(getNewFileName(), std::ios::binary | std::ios::trunc);        // 此处我设定得是清空，从头开始写，如果要追加得方式写的话，可修改为app
            assert(_ofs.is_open());
            _cur_file_size = 0;     // 新开，记录的大小自然是0，即什么都没有
        }
        // 获取要创建的文件名
        std::string getNewFileName() // 封装，更美观些
        {
            std::stringstream ss;
            ss << _base_file_path_name;     // 基础名，后面操作是跟时间和避免创建失败的文件计数
            struct tm t;
            time_t now_time = util::Date::nowDate();
            localtime_r(&now_time, &t);
            ss << t.tm_year + 1900;         // 时间戳是从1900开始的，要获取"现在"的时间得加上它
            ss << t.tm_mon + 1;             // 它是从0开始的
            ss << t.tm_mday;
            ss << t.tm_hour;
            ss << t.tm_min;
            ss << t.tm_sec;
            ss << "-"; // 美观
            ss << _file_count++;        // 文件计数器，避免文件创建失败的
            ss << ".log";           // 后缀，可加可不加，为了美观
            return ss.str();
        }

    private:
        std::string _base_file_path_name; // 基本文件名
        std::ofstream _ofs;               // 方便操作，不会在一次输出时，因为调用函数log的开始与结束，进行多次重复关闭与打开对应文件
        int _max_size;                    // 每个文件最多能写多少
        int _cur_file_size;               // 当前文件大小，即对应文件已经写入了多少
        int _file_count;                  // 文件计数器。防止在一秒内多次创建文件，而文件名相同导致创建失败的情况
    };

    // 工厂模式的落地方式创建，为了方便
    class SinkFactory
    {
    public:
        template <typename sinkType, typename... Args>
        static Sink::ptr createSink(Args &&...args)
        {
            return std::make_shared<sinkType>(std::forward<Args>(args)...);
        }
    };
}

#endif